# -*- coding: utf8 -*-
########################################################################
# Copyright (c) 2008 Burdakov Daniel <kreved@kreved.org>               #
#                                                                      #
# This file is part of PyWoW.                                          #
#                                                                      #
# PyWoW is free software: you can redistribute it and/or modify        #
# it under the terms of the GNU General Public License as published by #
# the Free Software Foundation, either version 3 of the License, or    #
# (at your option) any later version.                                  #
#                                                                      #
# PyWoW is distributed in the hope that it will be useful,             #
# but WITHOUT ANY WARRANTY; without even the implied warranty of       #
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        #
# GNU General Public License for more details.                         #
#                                                                      #
# You should have received a copy of the GNU General Public License    #
# along with PyWoW.     If not, see <http://www.gnu.org/licenses/>.    #
########################################################################

from twisted.internet.protocol import ServerFactory
from twisted.internet import reactor
from constants import *
from worldpacketprotocol import WorldPacketProtocol
from opcodes import opcodes
import SRP
from struct import pack, unpack, calcsize
from errors import *
import time
import packets

class WorldServerProtocol(WorldPacketProtocol):
 host_opcode_fmt = '<H'
 peer_opcode_fmt = '<L'
 def connectionMade(self):
  WorldPacketProtocol.connectionMade(self)
  self.status = NOTHING
  self.sseed = SRP.random_string(4)
  self.status = SENT_CHALLENGE
  self.character = None
  self.started = time.time()
  self.send(opcodes.SMSG_AUTH_CHALLENGE, self.sseed)
 def packetReceived(self, opcode, body):
  if self.status == SENT_CHALLENGE:
   # accept only CMSG_AUTH_SESSION packets
   if opcode == opcodes.CMSG_AUTH_SESSION:
    self.build, _ = unpack("<LL", body[:8])
    body = body[8:]
    t = body.find('\x00')
    self.username, body = body[:t], body[t+1:]
    if self.factory.isLocked(self.username) == REALM_LOCKED:
     self.send(opcodes.SMSG_AUTH_RESPONSE, chr(AUTH_UNAVAILABLE))
     return
    cseed, cdigest = unpack("<4s20s", body[:24])
    addons = packets.CAddonList()
    addons.unpack(body[24:])
    try: K = self.factory.getSessionKey(self.username)
    except NoSuchUser:
     self.send(opcodes.SMSG_AUTH_RESPONSE, chr(AUTH_UNKNOWN_ACCOUNT))
     return
    sdigest = self.calcAuthDigest(self.username, self.sseed, cseed, K)
    if sdigest == cdigest:
     self.factory.kickByUsername(self.username)
     self.factory.addSession(self)
     self.crypt.setKey(K)
     self.crypt.enable()
     response = pack("<BLBLB", AUTH_OK, 0, 0, 0, 1)
     # need some delay to prevent packet dropping by client (why client is so stupid?)
     self.status = WAIT
     reactor.callLater(0.1, self.send, opcodes.SMSG_AUTH_RESPONSE, response)
     reactor.callLater(0.2, self.handleAddonList, addons.list)
    else:
     self.send(opcodes.SMSG_AUTH_RESPONSE, chr(AUTH_FAILED))
   else:
    self.transport.loseConnection()
  else:
   self.handlePacket(opcode, body)
 def handleAddon(self, (name, crc)):
  standart_crc = 0x1c776d01L
  tdata = '\xc3[P\x84\xb9>2B\x8c\xd0\xc7H\xfa\x0e]TZ\xa3\x0e\x14\xba\x9e\r\xb9]\x8b\xee\xb6\x84\x93E'+\
  'u\xff1\xfe/d?=m\x07\xd9D\x9b@\x85Y4N\x10\xe1\xe7Ci\xef|\x16\xfc\xb4\xed\x1b\x95(\xa8#vQ1W0+'+\
  'y\x08P\x10\x1cJ\x1a,\xc8\x8b\x8f\x05-"=\xdbZ$z\x0f\x13P7\x8fZ\xcc\x9e\x04D\x0e\x87\x01\xd4\xa3\x15\x94\x16'+\
  '4\xc6\xc2\xc3\xfbI\xfe\xe1\xf9\xda\x8cP<\xbe,\xbbW\xedF\xb9\xad\x8b\xc6\xdf\x0e\xd6\x0f\xbe\x80'+\
  '\xb3\x8b\x1ew\xcf\xad"\xcf\xb7K\xcf\xfb\xf0k\x11E-z\x81\x18\xf2\x92~\x98V]^ir\n\r\x03\n\x85\xa2'+\
  '\x85\x9c\xcb\xfbVn\x8fD\xbb\x8f\x02"hc\x97\xbc\x85\xba\xa8\xf7\xb5@h<w\x86oK\xd7\x88\xca\x8a\xd7'+\
  '\xce6\xf0En\xd5dy\x0f\x17\xfcd\xdd\x10o\xf3\xf5\xe0\xa6\xc3\xfb\x1b\x8c)\xef\x8e\xe54\xcb\xd1*\xce'+\
  'y\xc3\x9a\r6\xea\x01\xe0\xaa\x91 T\xf0r\xd8\x1e\xc7\x89\xd2'
  if crc != standart_crc:
   msg = '\x01' + tdata
  else: msg = '\x00'
  return '\x02\x01' + msg + '\x00\x00\x00\x00\x00'
 def handleAddonList(self, addons):
  msg = ''.join(map(self.handleAddon, addons))
  self.send(opcodes.SMSG_ADDON_INFO, msg)
  self.status = AUTHENTICATED
 def kick(self):
  self.transport.loseConnection()
 def connectionLost(self, reason):
  self.factory.removeSession(self)

class WorldServerFactory(ServerFactory):
 protocol = WorldServerProtocol
 realmserver = None
 reactor = None
 def __init__(self, ip='127.0.0.1', port=3725, name="PyWoW Warzone", type=REALM_NORMAL, maxUsersOnline = 100):
  self.ip, self.port, self.name, self.type, self.maxUsersOnline = ip, port, name, type, maxUsersOnline
  self.sessions = []
  self.handlers = []
 def isLocked(self, username):
  """This function returns REALM_LOCKED if realm is inaccessible for
  username, and REALM_UNLOCKED else."""
  return REALM_UNLOCKED
 def getAddress(self):
  return "%s:%s" % (self.ip, self.port)
 def populationLevel(self):
  return len(self.sessions) * 2.0 / self.maxUsersOnline
 def amountOfCharacters(self, username):
  return 0
 def getSessionKey(self, username):
  """This function should return K, which was generated by reamserver,
  or raise NoSuchUser exception if such username was not found"""
  if self.realmserver:
   try: return self.realmserver.keys[username]
   except KeyError: raise NoSuchUser(username)
  else: raise NoSuchUser(username)
 def kickByUsername(self, username):
  for s in self.sessions:
   if s.username == username:
    s.kick()
 def addSession(self, session):
  self.sessions.append(session)
 def removeSession(self, session):
  if session in self.sessions:
   self.sessions.remove(session)
   if session.status == IN_WORLD:
    self.characterLogout(session)
 def characterLogin(self, session):
  pass
 def characterLogout(self, session):
  pass
 def handlePacket(self, session, opcode, body):
  pass
